package com.gitee.wsl.doc.prosemirror.state.selection

import com.gitee.wsl.doc.prosemirror.exception.RangeError
import com.gitee.wsl.doc.prosemirror.model.fragment.Fragment
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.content.ResolvedPos
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.state.bookmark.NodeBookmark
import com.gitee.wsl.doc.prosemirror.transform.map.Mappable
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put

// A node selection is a selection that points at a single node. All nodes marked
// [selectable](#model.NodeSpec.selectable) can be the target of a node selection. In such a selection, `from` and `to`
// point directly before and after the selected node, `anchor` equals `from`, and `head` equals `to`..
@Suppress("EqualsWithHashCodeExist")
class NodeSelection private constructor(
    // Create a node selection. Does not verify the validity of its argument.
    _pos: ResolvedPos,
    // The selected node.
    val node: Node,
    val _end: ResolvedPos,
    allowActions: Boolean = false
) : Selection(_pos, _end, allowActions) {

    private constructor(_pos: ResolvedPos, node: Node, allowActions: Boolean) : this(
        _pos,
        node = _pos.nodeAfter!!,
        _end = _pos.node(0).resolve(_pos.pos + node.nodeSize),
        allowActions = allowActions
    )

    constructor(_pos: ResolvedPos, allowActions: Boolean = false) : this(_pos, _pos.nodeAfter!!, allowActions)

    @Suppress("ktlint:standard:property-naming")
    override fun map(doc: Node, mapping: Mappable): Selection {
        val (deleted, pos) = mapping.mapResult(this.anchor).run { deleted to pos }
        val _pos = doc.resolve(pos)
        if (deleted) return near(_pos)
        return NodeSelection(_pos)
    }

    override fun content() = Slice(Fragment.Companion.from(this.node), 0, 0)

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is NodeSelection) return false
        if (anchor != other.anchor) return false
        return true
    }

    override fun toJSON() = buildJsonObject {
        put("type", "node")
        put("anchor", anchor)
    }

    override fun getBookmark() = NodeBookmark(this.anchor)

    override fun toString(): String {
        return "NodeSelection($node)"
    }

    companion object : SelectionClass {
        init {
            jsonID("node", this)
        }

        override fun fromJSON(doc: Node, json: JsonObject): NodeSelection {
            val anchor = json["anchor"]?.jsonPrimitive?.intOrNull
                ?: throw RangeError("Invalid input for NodeSelection.fromJSON")
            return NodeSelection(doc.resolve(anchor))
        }

        // Create a node selection from non-resolved positions.
        fun create(doc: Node, from: Int) = NodeSelection(doc.resolve(from))

        // Determines whether the given node may be selected as a node selection.
        fun isSelectable(node: Node) = !node.isText && node.type.spec.selectable
    }

    override fun hashCode(): Int {
        var result = node.hashCode()
        result = 31 * result + _end.hashCode()
        return result
    }
}